{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ac08f754-53d0-4323-a1df-f8f785bb7484",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import Image"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e537a4e-893a-4cd2-b2ba-bc6d554f10d3",
   "metadata": {},
   "source": [
    "- https://www.anthropic.com/engineering/building-effective-agents\n",
    "    - https://mirror-feeling-d80.notion.site/Workflow-And-Agents-17e808527b1780d792a0d934ce62bee6\n",
    "        - https://langchain-ai.github.io/langgraph/tutorials/workflows/\n",
    "        - https://www.youtube.com/watch?v=aHCDrAbH_go\n",
    "- AlphaEvolve: coding agent\n",
    "    - llm 作为核心算子实现遗传算法；\n",
    "    - 适用于可以自动评估的环境；& 大量脚手架类似的工作；\n",
    "- https://github.com/google-gemini/gemini-fullstack-langgraph-quickstart/tree/main\n",
    "    - 基于 langgraph 编排工作流实现 deepresearch\n",
    "        - 不同的节点用不同的 gemini models\n",
    "        - pro：deep，flash：width\n",
    "- sota llms (gemini 2.5 pro, openai o3) + 精心设计的 workflow（脚手架，scaffolding）能解决非常多非常复杂的问题；\n",
    "    - 基础模型实在是越来越powerful了"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "54e0cc6b-eb8c-468a-b128-fbdcce6de19d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://mirror-feeling-d80.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fc2810b1e-a85a-492f-bc51-5aa2decfa5ac%2Ffb35bebb-2ad8-48d5-ad83-b93dd3f6e6b8%2Fimage.png?table=block&id=18080852-7b17-8024-82ab-d5bd13b9ff42&spaceId=c2810b1e-a85a-492f-bc51-5aa2decfa5ac&width=1420&userId=&cache=v2\" width=\"800\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://mirror-feeling-d80.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2Fc2810b1e-a85a-492f-bc51-5aa2decfa5ac%2Ffb35bebb-2ad8-48d5-ad83-b93dd3f6e6b8%2Fimage.png?table=block&id=18080852-7b17-8024-82ab-d5bd13b9ff42&spaceId=c2810b1e-a85a-492f-bc51-5aa2decfa5ac&width=1420&userId=&cache=v2', \n",
    "      width=800)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "41490ee8-605f-4b68-ac46-f2f382a41dbf",
   "metadata": {},
   "source": [
    "- workflows\n",
    "    - Create a scaffolding of predefined code paths around llm calls\n",
    "    - LLMs directs control flow through predefined code paths\n",
    "- Agen: remove this scaffolding (LLM directs its own actions, responds to feedback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e064d92b-c0f1-41b7-8ae9-855ab735b575",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from dotenv import load_dotenv\n",
    "assert load_dotenv()\n",
    "llm = ChatOpenAI(model='gpt-4o-mini')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "21058ea9-1b52-4bb1-8cac-f7028a119124",
   "metadata": {},
   "source": [
    "### Augmented LLM"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0994830-9f8e-4b1d-8d79-323617b0fbcc",
   "metadata": {},
   "source": [
    "- Augment the LLM with schema for structured output\n",
    "- Augment the LLM with tools"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "9616d82b-b6a7-47f8-818a-c03883bd0e1b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/augmented_llm.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/augmented_llm.png', width=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "fb085d49-946a-4143-ba65-c6446351dba2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Schema for structured output => output schema\n",
    "from pydantic import BaseModel, Field\n",
    "\n",
    "class SearchQuery(BaseModel):\n",
    "    search_query: str = Field(None, description=\"Query that is optimized web search.\")\n",
    "    justification: str = Field(\n",
    "        None, description=\"Why this query is relevant to the user's request.\"\n",
    "    )\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "structured_llm = llm.with_structured_output(SearchQuery)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "ea47cfdc-332e-44ee-a86a-b533d24d679a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Invoke the augmented LLM\n",
    "output = structured_llm.invoke(\"How does Calcium CT score relate to high cholesterol?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a1e415a1-cc98-4408-862c-0638552ec494",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SearchQuery(search_query='Calcium CT score high cholesterol relationship', justification='This query targets the relationship between calcium CT scores and cholesterol levels, which may help in understanding cardiovascular risk assessment.')"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "dd0616fa-2f57-4a2e-9ad3-71d32f86e7de",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SearchQuery(search_query='2023年高考 新闻 相关报道', justification='搜索2023年高考的相关新闻，以获取该年度高考的最新动态、政策变化及新闻事件等信息，符合用户对今年高考的关注。')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "structured_llm.invoke(\"今年高考新闻\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3b9e8095-160c-4aa6-9c43-10899d9ad3fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define a tool\n",
    "def multiply(a: float, b: float) -> float:\n",
    "    return a * b\n",
    "def sigmoid(a: float) -> float:\n",
    "    return 1./(1+np.exp(-a))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "6dfa03b0-1659-4a05-a6aa-607cdd5ae42f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Augment the LLM with tools\n",
    "llm_with_tools = llm.bind_tools([multiply, sigmoid])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "5e2d2597-6e02-4416-8830-70b55209d26c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Invoke the LLM with input that triggers the tool call\n",
    "msg = llm_with_tools.invoke(\"What is derivative of sigmoid(5)\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "9cf78041-3088-4e10-86ab-fd5808db4f6c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'name': 'sigmoid',\n",
       "  'args': {'a': 5},\n",
       "  'id': 'call_PBKMYMxZjU0x8IP9TxMHuE8Y',\n",
       "  'type': 'tool_call'}]"
      ]
     },
     "execution_count": 95,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "msg.tool_calls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "34c71451-70c3-482a-ab25-eb4f0620813e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_PBKMYMxZjU0x8IP9TxMHuE8Y', 'function': {'arguments': '{\"a\":5}', 'name': 'sigmoid'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 194, 'total_tokens': 208, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BiJpUWnwANEG7bDWV9FR6mXp5vHj1', 'service_tier': 'default', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--ac1635ef-b4cd-4526-af25-f1ba23e22ed7-0', tool_calls=[{'name': 'sigmoid', 'args': {'a': 5}, 'id': 'call_PBKMYMxZjU0x8IP9TxMHuE8Y', 'type': 'tool_call'}], usage_metadata={'input_tokens': 194, 'output_tokens': 14, 'total_tokens': 208, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})"
      ]
     },
     "execution_count": 96,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "msg"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "aadfe4bc-c8c1-45d0-aa7c-fc34e9e4095f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://python.langchain.com/assets/images/with_structured_output-4fd0fdc94f644554d52c6a8dee96ea21.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://python.langchain.com/assets/images/with_structured_output-4fd0fdc94f644554d52c6a8dee96ea21.png', width=500)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6d13a54-1268-4b0f-9877-681dab4875d1",
   "metadata": {},
   "source": [
    "### Prompt chaining"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "84c317b5-acd0-4581-9959-b7a5d7c55b98",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/prompt_chain.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/prompt_chain.png', width=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "90afec82-abf5-4dfe-8813-b47544007c59",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing_extensions import TypedDict\n",
    "from langgraph.graph import StateGraph, START, END\n",
    "from IPython.display import Image, display"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "c128252b-4ac7-4f40-b1d3-fce4048e195d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    improved_joke: str\n",
    "    final_joke: str"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "f391b4f9-78ec-410a-b269-7c8a3b59aec5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nodes\n",
    "def generate_joke(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a short joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def check_punchline(state: State):\n",
    "    \"\"\"Gate function to check if the joke has a punchline\"\"\"\n",
    "\n",
    "    # Simple check - does the joke contain \"?\" or \"!\"\n",
    "    if \"?\" in state[\"joke\"] or \"!\" in state[\"joke\"]:\n",
    "        return \"Pass\"\n",
    "    return \"Fail\"\n",
    "\n",
    "\n",
    "def improve_joke(state: State):\n",
    "    \"\"\"Second LLM call to improve the joke\"\"\"\n",
    "    # wordplay: 俏皮话/双关语\n",
    "    msg = llm.invoke(f\"Make this joke funnier by adding wordplay: {state['joke']}\")\n",
    "    return {\"improved_joke\": msg.content}\n",
    "\n",
    "\n",
    "def polish_joke(state: State):\n",
    "    \"\"\"Third LLM call for final polish\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Add a surprising twist to this joke: {state['improved_joke']}\")\n",
    "    return {\"final_joke\": msg.content}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "300bc490-7dc8-40e7-8255-8588d72a94c2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build workflow\n",
    "workflow = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "workflow.add_node(\"generate_joke\", generate_joke)\n",
    "workflow.add_node(\"improve_joke\", improve_joke)\n",
    "workflow.add_node(\"polish_joke\", polish_joke)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "workflow.add_edge(START, \"generate_joke\")\n",
    "workflow.add_conditional_edges(\n",
    "    \"generate_joke\", check_punchline, {\"Fail\": \"improve_joke\", \"Pass\": END}\n",
    ")\n",
    "workflow.add_edge(\"improve_joke\", \"polish_joke\")\n",
    "workflow.add_edge(\"polish_joke\", END)\n",
    "\n",
    "# Compile\n",
    "chain = workflow.compile()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "77ef2adc-40c6-4f8e-8649-96ed7e1f8ebf",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Show workflow\n",
    "display(Image(chain.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "14e428d2-dfa5-4021-8e9c-a66ea233743b",
   "metadata": {},
   "outputs": [],
   "source": [
    "state = chain.invoke({\"topic\": \"cats\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "f61f8f56-f168-46ea-9d5f-516d51a5b870",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'topic': 'cats',\n",
       " 'joke': 'Why did the cat sit on the computer?\\n\\nBecause it wanted to keep an eye on the mouse!'}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "state"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "a0e196ec-8aff-4b6e-ac83-51d9a8f8fcb4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'generate_joke': {'joke': \"Why did the dog sit in the shade? \\n\\nBecause he didn't want to become a hot dog!\"}}\n"
     ]
    }
   ],
   "source": [
    "for step in chain.stream({\"topic\": \"dogs\"}):\n",
    "    print(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64fb8256-7672-42a5-a88f-ca969fac0724",
   "metadata": {},
   "source": [
    "### Parallelization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8daa38e4-8b85-467b-874b-e09399cbab93",
   "metadata": {},
   "source": [
    "- LLMs can sometimes work **simultaneously** on a task and have their outputs **aggregated** programmatically. This workflow, parallelization, manifests in two key variations:\n",
    "    - Sectioning: Breaking a task into independent subtasks run in parallel.\n",
    "    - Aggregator(Voting): Running the same task multiple times to get diverse outputs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "92f045f1-7126-4b7d-8b21-d7e4789c451e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/parallelization.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/parallelization.png', width=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "70700f22-a5a2-4e29-9782-94402aec8594",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str\n",
    "    joke: str\n",
    "    story: str\n",
    "    poem: str\n",
    "    combined_output: str\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def call_llm_1(state: State):\n",
    "    \"\"\"First LLM call to generate initial joke\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_2(state: State):\n",
    "    \"\"\"Second LLM call to generate story\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a story about {state['topic']}\")\n",
    "    return {\"story\": msg.content}\n",
    "\n",
    "\n",
    "def call_llm_3(state: State):\n",
    "    \"\"\"Third LLM call to generate poem\"\"\"\n",
    "\n",
    "    msg = llm.invoke(f\"Write a poem about {state['topic']}\")\n",
    "    return {\"poem\": msg.content}\n",
    "\n",
    "\n",
    "def aggregator(state: State):\n",
    "    \"\"\"Combine the joke and story into a single output\"\"\"\n",
    "\n",
    "    combined = f\"Here's a story, joke, and poem about {state['topic']}!\\n\\n\"\n",
    "    combined += f\"STORY:\\n{state['story']}\\n\\n\"\n",
    "    combined += f\"JOKE:\\n{state['joke']}\\n\\n\"\n",
    "    combined += f\"POEM:\\n{state['poem']}\"\n",
    "    return {\"combined_output\": combined}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "9f88613f-5fc9-457c-9cd3-aa76d62d2d71",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build workflow\n",
    "parallel_builder = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "parallel_builder.add_node(\"call_llm_1\", call_llm_1)\n",
    "parallel_builder.add_node(\"call_llm_2\", call_llm_2)\n",
    "parallel_builder.add_node(\"call_llm_3\", call_llm_3)\n",
    "parallel_builder.add_node(\"aggregator\", aggregator)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "parallel_builder.add_edge(START, \"call_llm_1\")\n",
    "parallel_builder.add_edge(START, \"call_llm_2\")\n",
    "parallel_builder.add_edge(START, \"call_llm_3\")\n",
    "parallel_builder.add_edge(\"call_llm_1\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_2\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"call_llm_3\", \"aggregator\")\n",
    "parallel_builder.add_edge(\"aggregator\", END)\n",
    "parallel_workflow = parallel_builder.compile()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4a9e4acd-d513-465a-beef-1e721a87cc70",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Show workflow\n",
    "display(Image(parallel_workflow.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "d258065e-e770-40ef-aa1a-133603349cee",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Invoke\n",
    "state = parallel_workflow.invoke({\"topic\": \"cats\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "67992fe2-ad7b-46a4-9c07-66d40a8805b6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_keys(['topic', 'joke', 'story', 'poem', 'combined_output'])"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "state.keys()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "14a0710f-302c-45b3-a426-93279a683841",
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(state['combined_output'])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80dfba10-1773-48a8-b605-2d79b8c98972",
   "metadata": {},
   "source": [
    "### Routing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "02e13ea2-62c0-4361-9936-2f58195cecfe",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/routing.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/routing.png', width=500)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cb3d2d0-ff21-46e3-a59c-618fbd0b46e8",
   "metadata": {},
   "source": [
    "- Routing classifies an input and directs it to a specialized followup task. This workflow allows for **separation** of concerns, and building more specialized prompts. Without this workflow, optimizing for one kind of input can hurt performance on other inputs.\n",
    "- When to use this workflow: Routing works well for **complex tasks** where there are **distinct categories** that are better handled separately, and where classification can be handled accurately, either by an LLM or a more traditional classification model/algorithm.\n",
    "    - distinct models (fast non-reasoning model, powerful reasoning model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "b9b48b16-c2c9-4c8f-bd84-c5714eff64dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing_extensions import Literal\n",
    "from langchain_core.messages import HumanMessage, SystemMessage"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "52664bcf-6e37-4389-89c1-e5e2b5d50916",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Schema for structured output to use as routing logic\n",
    "class Route(BaseModel):\n",
    "    step: Literal[\"poem\", \"story\", \"joke\"] = Field(\n",
    "        None, description=\"The next step in the routing process\"\n",
    "    )\n",
    "\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "router = llm.with_structured_output(Route)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "87d90e85-1c79-44c3-89cb-2ab0060f643a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# State\n",
    "class State(TypedDict):\n",
    "    input: str\n",
    "    decision: str\n",
    "    output: str"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "f55f37e6-36da-4158-924d-a86d2b2cb520",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nodes\n",
    "def llm_call_1(state: State):\n",
    "    \"\"\"Write a story\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n",
    "\n",
    "\n",
    "def llm_call_2(state: State):\n",
    "    \"\"\"Write a joke\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n",
    "\n",
    "\n",
    "def llm_call_3(state: State):\n",
    "    \"\"\"Write a poem\"\"\"\n",
    "\n",
    "    result = llm.invoke(state[\"input\"])\n",
    "    return {\"output\": result.content}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "2c53df88-88d1-48a7-ada2-3d93fcf615b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def llm_call_router(state: State):\n",
    "    \"\"\"Route the input to the appropriate node\"\"\"\n",
    "\n",
    "    # Run the augmented LLM with structured output to serve as routing logic\n",
    "    decision = router.invoke(\n",
    "        [\n",
    "            SystemMessage(\n",
    "                content=\"Route the input to story, joke, or poem based on the user's request.\"\n",
    "            ),\n",
    "            HumanMessage(content=state[\"input\"]),\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    return {\"decision\": decision.step}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "7123ffa7-060f-4a6f-aa7b-ac97c98b38e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Conditional edge function to route to the appropriate node\n",
    "def route_decision(state: State):\n",
    "    # Return the node name you want to visit next\n",
    "    if state[\"decision\"] == \"story\":\n",
    "        return \"llm_call_1\"\n",
    "    elif state[\"decision\"] == \"joke\":\n",
    "        return \"llm_call_2\"\n",
    "    elif state[\"decision\"] == \"poem\":\n",
    "        return \"llm_call_3\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "517925c7-2887-4c83-8deb-4a9b11c29c8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build workflow\n",
    "router_builder = StateGraph(State)\n",
    "\n",
    "# Add nodes\n",
    "router_builder.add_node(\"llm_call_1\", llm_call_1)\n",
    "router_builder.add_node(\"llm_call_2\", llm_call_2)\n",
    "router_builder.add_node(\"llm_call_3\", llm_call_3)\n",
    "router_builder.add_node(\"llm_call_router\", llm_call_router)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "router_builder.add_edge(START, \"llm_call_router\")\n",
    "router_builder.add_conditional_edges(\n",
    "    \"llm_call_router\",\n",
    "    route_decision,\n",
    "    {  # Name returned by route_decision : Name of next node to visit\n",
    "        \"llm_call_1\": \"llm_call_1\",\n",
    "        \"llm_call_2\": \"llm_call_2\",\n",
    "        \"llm_call_3\": \"llm_call_3\",\n",
    "    },\n",
    ")\n",
    "router_builder.add_edge(\"llm_call_1\", END)\n",
    "router_builder.add_edge(\"llm_call_2\", END)\n",
    "router_builder.add_edge(\"llm_call_3\", END)\n",
    "\n",
    "# Compile workflow\n",
    "router_workflow = router_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "e8a5e5d6-6d97-459e-9ed9-fdb883ef2030",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAaMAAAFNCAIAAAAiuZdRAAAAAXNSR0IArs4c6QAAIABJREFUeJzt3XdcU1f/B/CTRRYhIKDIkCkyFERBAfe2ap0dPm4q1tVaf1af2qpVa3e1te5ZW0ut2tbRWrXUUesAC6jIsCgyBGQmEMgkNze/P65PShEUhOQk937fL/8AAslHDvnk3HNv7mUZjUYEAAC0xsYdAAAAzA6aDgBAf9B0AAD6g6YDANAfNB0AgP6g6QAA9MfFHQBYo7JCnbqWUCkIgjDWa0jccZ6OL2RzeCyxA1ck4br58HHHAVaHBcfTAZPs67UFWaq8LJVviJjFRmIHrlMnO53agDvX09kJOTUV9apawmhkFWQrfUPFvqHi4D4OuHMBawFNBxBC6NalmpREuV8Pe99QsW93MYuFO1AbkCQqyFLlZ6nu31b2Hd0hfKAj7kQAP2g6pivN154+UNqttyR2nAubgztNuzIQxmunZPfTlaPnuLl5C3DHAThB0zFaxlXF3bS65+I6iyT0KrkG1HWGX/eXBveVdI+R4s4CsIGmY667N+oe5mkHv+CKO4glXPyh0itAGBBhjzsIwAOajqGST8vUdeTQlxlRc5Tzh8sljrw+ozvgDgIwgOPpmCj3lrKmUs+omkMIDZvaqapUl5ehwh0EYABNxzjVlfrcdOXo2W64g2AwJq5zTlqdokqPOwiwNGg6xrlyvJLJB5oFRUkun6zCnQJYGjQdszy8r9HXk97BItxBsPENFWtVhtICLe4gwKKg6Zgl+3pd/wkdcafAbMBE1+wkBe4UwKKg6RhEXWco/FvV0cvOkg965MiRtWvXPsMPDh8+vKSkxAyJUKcu/PwslVZlA+/nBe0Fmo5B8rNUvqFiCz9oVlbWM/xUcXFxTU2NGeI84hMqzs9Smu/+gbWB4+kY5OLRCv8wSZcgoTnuPC8vb/fu3ampqRwOJywsbObMmeHh4XPnzk1PT6e+ISEhISgo6MiRI5cvX87MzOTz+ZGRkYsXL3Z3d0cILV++3M7Ozs3N7eDBg/Hx8fv27aN+atCgQZs2bWr3tIXZ6vws1eAXmXWcDZPBnI5BHuZpJB3Mcp6u+vr6BQsWGAyG3bt3b926lc1mL1u2TKfT7d+/v3v37mPHjk1NTQ0KCkpLS/vss88iIiISEhI2b95cXl6+Zs0a6h54PF52dnZubu7nn3/+8ssvb968GSF08uRJc9QcQsjeifswX2OOewbWCc5PxyCqWoPYwSzvby0sLJTL5XPmzAkICEAIffTRRzdv3iQIgs//16nievbseeTIER8fHw6HgxCaMWPG8uXLlUqlvb09h8OprKw8cuRIox8xE7EDV11LWOCBgJWApmMKQm80GIx2ArPM4rt06eLk5LRu3bopU6aEh4eHhIRERkY+/m0cDqeoqGjTpk0ZGRkazaMplVwut7e3Rwj5+vpapuYQQgIxu15LkgZEs9O3gObA1itTkCTiC831tObz+Xv37u3fv//+/ftnzZo1adKks2fPPv5tFy5cWL58eVhY2P79+1NSUqhN1IZ3YqZ4TeKLOEYSFqmZApqOKez4LL3WoNeZ67nt4+OzdOnSU6dObdy40c/Pb/Xq1Xfv3m30PcePH4+IiFiwYEFgYCCLxVIqse391GlIA2Hk8Gz5jKOgNaDpGETkwFWZZ3EqPz//l19+QQgJBILBgwd/8sknbDY7Ozu70bcpFApX1392d168eNEcYVpCXUuYackSWCdoOgbxCBCq68zSdNXV1evXr9+8eXNxcXFeXt6BAwdIkgwLC0MIeXl5ZWdnp6amyuXywMDAv/7668aNGwRBJCQkcLlchFBZWdnjd+jj44MQOnfuXGZmpjkCq+tId3/mviWOgaDpGMTZzS433SwbjL169XrnnXfOnDkzceLEl156KT09fffu3X5+fgihyZMnG43GRYsW3bt377XXXuvTp8/SpUtjYmKqqqrWrl0bEhKyaNGic+fONbpDT0/P559/fufOnVu3bjVH4Nz0Ohd3i75XBOAFRw4zSK2cOL69ePYaH9xB8DuwvuDFNzztHeHYA6aAOR2DOHTgduoiqKlg+tnZ5GX17n5CqDlGgcFmlsBekmunqsa80rm5b4iPj8/NzX386wRBIISolbXHnTp1ijomrt3dvn17yZIlTd5EEERzeajdHaxmLuZ47VQVXD2HaWDrlXF++LJ4wESX5q4KWFlZqdc3PenT6XTNHfJGvXfVTB4+fPgMP9VcpNI87bVfq6a87tnmXMCWQNMxTlmBNvt67dCXGXqWuvOHK7rHSjt1sehRygA7WKdjHDcfQQc3u8snmHiG8UvHKl09+VBzDARNx0Q9BznWa8nU36txB7Gov36Tk4QxrD+s0DERbL0yV8rv1SwWihzuhDuIJfyVKGOz2JEjGPGfBY+DOR1zRY1wqteQiQnluIOY3W8HywwEgppjMpjTMV1OWt2FIxWx45zDBzriztL+bl2qST4tGza1Y9cICe4sACdoOoAMeuPVU1WF2eqgKIlvqNjFw+YX7CtLdPlZqjvXa/162Pd73gVOQgeg6cAjqlpD5lVFfpZSqyZ9QsRcHkvswHVw5hF6G7iGFpfHVsjq1bUGQm/Mz1IK7bm+oeKwflKhBEoOIGg60ARlDVFWqFPW6FUKgsVitfuJni5dujRo0KD2vU+RA4c6Z7q9I6+zD18shTf/gH+BpgOWFhUVlZKSgjsFYBbY9woAoD9oOgAA/UHTAQDoD5oOAEB/0HQAAPqDpgMA0B80HQCA/qDpAAD0B00HAKA/aDoAAP1B0wEA6A+aDgBAf9B0AAD6g6YDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQHzQdAID+oOmApbm4uOCOABgHmg5YWlVVFe4IgHGg6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQHzQdAID+oOkAAPQHTQcAoD9oOgAA/UHTAQDoD5oOAEB/0HQAAPqDpgMA0B/LaDTizgAYISIigsViIYRYrH/+6m7cuIE7F2AEmNMBC3F3d2ez2Ww2m8ViUR+4u7vjDgWYApoOWEjPnj1JkjR9ajQaQ0NDsSYCDAJNByzkpZdeajiJc3d3nzlzJtZEgEGg6YCFhIeHh4SEmD4NCwvr3r071kSAQaDpgOVMnz6dujCYq6vrf/7zH9xxAINA0wHLCQ8Pp9bmevToARM6YElc3AFAqxmNqKJIV1NRr68nW/Dt1mVE31dqi6VDek/OvKbAnaXVeHyOkyvP1YvPYuGOAloJjqezMYV31KnnqvX1pLu/WKcy4I7DLHwx+2GumsdnR4106tJNhDsOaAWY09mSskJd8hn5c3GeLFh1wCRiiDNpQGe/LrYTcNy8+bjjgJaCZ4zNUFTpf/u2bMxcqDnM2Bw0Zq7n2W9Ka+UE7iygpeBJYzNSz1VHjXTFnQI8EjXKNe28HHcK0FLQdDbj4X2N1IWHOwV4ROrMK7mvwZ0CtBQ0nY0wIkJvFEthXdVaiKU8Qgd782wGNJ2NYCGt2oDgmWU1jAhpNbDv22ZA0wEA6A+aDgBAf9B0AAD6g6YDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmo62fjh0ePrIv9fHEycMPfrsPV5K8vNwhwyIzMm4hhNatf2v5ikW4kgDGgqYDtLVu/Vunz5zEnQJYBWg6QFt/52ThjgCsBTQds+Tm3h0yLDL5+tU3/m/ekGGR06aP/+XUsTt3MmfNmTJ8ZN/Xlrxy997fT70TRa3i40/WDRkWOXHy8Pc/WFVZWUF9PSnp8gcfrn5p6pgx4wa8uXzhrVtpzxbyXm7OkGGRyclXXnhpdPyr/0EIaTSabds3zZg5ceTomJmzJ2/c9L5G8+gsmCNHxxw+ctD0sx99snbRa3MIghgyLLK8vOyzjRuenzCYuun0mZMLF89+bmz/xa/H/fjTIdO1ota8u3zD++/s3rNlyLDIkofFz5YZWDloOmaxs7NDCG3fsWnWzHkXzqWEhobt2bNly9ZP33l7w9nTV7lc7tZtnz35HvR6/dvvvKGorfl8067XX1tRVl668p0lBEGo1er3P1xFEMT6dZ8d2P+Dh4fXqjX/V1NT/SwheXYIoX1fbX/5pZlvLluNEPpyyycXLv62aOGyn35MjJuz4OIfiXv2bnnCPXC53LOnryKEVixf88vJPxBCv/9++rONG4K6hRxK+DluzoIffvxu+47PqW/m8Xg5Odl5+bkfbPjcuYPLMwQG1g+ajlnYbDZCaOL4F3v36sNisQYNHK5UKadNiwvqFsLlcgf2H5qbm/Pke7h67dKdO5kL5y+N6Bk5bOioxYve9PUNqK6Wi0SifXsPL31jZXBQaKdObq/OW6JWqzMz058hJIfDQQj1ix304gvTg4NCa+tqz184O3vWq7GxAyX2kqFDRk6eNDXx918JohUXrPnl12NhYRFvLHnLyalDZO++r8xZeOLkUYWihnq4Klnle+s+i40dKBAIniEwsH5wtm4m8vH1pz4Q29sjhLy7+FKfCoRCrVZLEASX2+wfRn5+rr29fZcuPtSnwUGhq995n/pYrVLt27ct/fYNmayK+kqN4lnmdJTArsHUB8XFDwiCCAnpYbqpW7cQtVpdWlri5eXdkrsiCCI7O2PO7Pmmr0RERBkMhoyMW/37D6Z+A3w+XNKQzqDpmIia2TX36ZMpVUqBQPj418vKSt/4v/ioyJg1qz4MCelBkuToMf3aEtLuf9Ujl1chhAT8f2ZbQqEIIaTWqFt4V1qt1mAw7P9qx/6vdjT8enWNvNFjAbqCpgOtIxaJ1WoVSZKN+vHCxd/0ev1b/11HbQCapnXt8Ihie4SQRvvPhbjUahVCyMW5iWtCkoYmru1gb28vEAhGj3p+4MBhDb/u4e7VXiGBlYN1OtA63QJD1Gp1zt071KcPHhQsXfZqXl6uQlEjkTiY1rku/Xm+vR7R3z+Qw+E0XPK7cydTKnXs0MEZIcTn8zUNJncPHhQ0eSd+fl01Wk1Ez0jqX2hImIuza8eOndorJLBy0HSgdfr27efh4bVnz5bLVy6mpCZv/vJjmayqSxefAP9Amazq19MnCIJIvn41I+Omg4O0oqKs7Y/oIHEYNmz0twn7rl37s05Zl5j46/ETR158YTqLxUIIhYaGX75yUaVSIYS+Tdgvkz+aS/L5fFfXjjdu/HXzVipBEPPnLfnzz/Onz5wkSfL27Zvvvf/2mysW6nS6tscDNgGaDrQOl8vd+OkO0ki+u3bFf996TSAUfrDhcy6XO3z4c9OnxR34eteIUdHHTxx5/bUVI0eM/TZh/5dbPmn7g76+eEVszMANH7wzecqIQ4e/njkjfurLsx7d9NoKR6nTuPGDRoyK1um0w4c9Z/jfPtnp015JTbu+5t03NVpNWFjE7p0Jt2/fnDRlxIq3FqtVqvc3fA57IZiDZTp+Eli5nf+9/5//+nF4LNxBAEII6euNRzflLfjYH3cQ0CIwpwMA0B/sewWNZWXdXvn2kuZu/f7QKXt7+zY+xJGj3yYk7G/yJl+/gC2bsZ12BdAVNB1oLDQ0bM+eQ83d2vaaQwiNGTOx0QEfJjwur+33D0Aj0HSgCZ3d3M16/xJ7icReYtaHAKAhWKcDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQHzSdzXBx5xsIOPGMtTAQRhd3OOmTzYCmsxl2AnZliRZ3CvBIVbGWL4Snj82AobIBCoVi69atsvrbxXdVuLOAR4rvqspVN7dt21ZbW4s7C3g6aDrrlZmZeezYMYRQVlaWg4PDlNkxIgd2aqIMdy6AUn+rkjhxXozrb29vn52djRA6duxYVlYW7lygWXDOYauTmZnZvXv34uLi1atXz5w5c9iwf53d6MLRCqORJRRzXTwEJAljZ1EsNquqRKtVEhwuGvzCv65MlpiYeOjQoY8++qhz587UCOKLCZoATWctdDodn8+fMGGCt7f3li1bDAYDdSn7xz3I0ZTkqrVqslamt3jMdpCXl+fn54c7xbOQdOAK7Tme/kKvbqImv4EatcWLF5eVlf30009ardZ0sTSAFzQdft99991XX311+PBhV1fXmpoaR0dH3InMKyoqKiUlBXcK86qurnZyciovL58+fXp8fPzUqVNxJ2I6aDo8CgsLDx482K9fv6FDh16+fDksLEwqleIOZSGM2rhTKBS3b98eMGDAuXPnkpOTZ82a1aVLF9yhmAiazqKuXLmiVqtHjhx54sQJFos1bty45jZRAc0QBHHq1Ck2mz1+/PjExESxWNyvXz/coRgE9r1aws2bNxFC58+f//HHH728vBBCEydOnDBhAjNrLi4uDncEDLhc7sSJE8ePH48Q8vDwOHr06B9//IEQunHjBu5ojABzOvOSyWSjR4+Oj4+fP3/+E3YyMAoT1ulagvp72LFjxzfffHP27FknJyfciegMms4s1q5d+8cff1y6dEmtVguFQhYLLkf9D0at07WEwWDQ6/UCgaBfv36jR49es2YN7kQ0BFuv7SYlJeXNN98sLCxECA0fPvzSpUsIIZFIBDXXCNRcIxwOhzoY5erVq4MGDUII3b9/f/ny5Wlpabij0Qc0XZtQy8zUX2RWVtb48eO9vb0RQgMGDMAdzXoxc52uhQYOHIgQ8vf3HzduHPWmi5SUlNOnTxsMBtzRbBtsvT4LhUJRWFgYFha2a9eusrKyRYsWdezYEXcomwHrdK1SXl6+Y8cOT0/PefPmpaen+/n5SSRwqdxWg6Zrhbq6OolEcuPGjRUrVqxcuXLEiBG4E9kkWKd7Zr/99tvHH3+8efPm8PBwpVJpb2+PO5HNgKZrEZVKtWzZMpFI9MUXX1CHv+NOBJiLeiPNkiVLdDrd5s2bhUIh7kQ2AJruSY4dO/b777/v3LlToVDk5ub27t0bdyI6iIuLO3DgAO4UdJCWlhYYGCiRSObPnz9mzJgJEybgTmS9YI9EYzKZ7Ntvvy0pKaGWSBYuXIgQkkqlUHPtJTMzE3cEmujduze1Zrdo0aKHDx8ihIqKihISEuRyOe5oVgfmdI88ePCgvr4+ICDgvffek0qlCxYs4PPh3NlmcefOneDgYNwp6Emr1e7atauurm7NmjV3794VCoXUe3IA05tOJpM5OzsfPXr08OHDH330Ubdu3XAnAqB9ZGdnr1q1asaMGVOmTKH+znEnwom5W68FBQUvv/zyyZMnEULDhg07duwY1JxlzJo1C3cERggJCTl+/PjgwYMRQj/99NPUqVMfPHiAOxQ2zJrTEQSxd+/eoqKiDz/8sLCwkCAIf39/3KEYB46nw+L+/ftcLtfb23vlypU+Pj7z5s1j1LuwGTGny8nJ2bFjB0KotrbWzs5uyZIlCCFvb2+oOSwOHjyIOwIT+fv7U2/gWbJkCZfLVSqVCKHt27ffu3cPdzRLoHPT3b59u7KykhpO6hjLDh06zJ07183NDXc0RoPdEXi5u7vHx8dTZ34ViUTbt2+nDjPIyMjAHc2MaLj1Si2+rl27tqioaNOmTXCUr7WZNWsWTOusjUwmW758uZ+f35o1a2i5+4JWc7pr166NGjXqzp07CKFly5Z99dVXUHNWiBogYFWcnZ0PHDhALexkZmaOHj06OTkZd6j2ZPNzOoVCsWPHDqFQuHTp0qysLDc3N/q9HNEMHE9n/WQyWXl5eUhIyBdffKHT6RYuXGjr1zmx1aa7evXqnTt34uPjMzIy7t69O2bMGHj3HwDtTq1Wnz59Ojg4ODQ0dM+ePd27d4+NjcUd6lnY2NYrdUCQXC4/evRoYGAgQqhHjx5TpkyBmrMhcDydDRGJRC+88EJoaChCqFu3bocPH1YoFCRJFhcX447WOjY2p5swYQJ1rC+wXS+99NLRo0dxpwDPzmAwvPjii8eOHcMdpBVsbE4H6zs0sH79etwRQJuwWCybe0ORjc3pAADgGdjYnO78+fO4I4C2gnU6Grhw4QLuCK1jY023cuVK3BFAW8HxdLbOYDC8/fbbuFO0jo013ciRI3FHAG313Xff4Y4A2oTNZtvcMxHW6QAA9Gdjc7rExETcEUBbTZ8+HXcE0CZGo/H333/HnaJ1bKzpVq1ahTsCaKu7d+/ijgDahCTJ1atX407ROjbWdDa3OgAeB+t0tg7W6QAAwBrZ2JwO1uloANbpbB2s05kdrNPRAKzT2TpYpzM7m1sdAI+DdTpbB+t0AABgjWxsTgfrdDQA63S2DtbpzA7W6WgA1ulsnS2u09nG1utzzz1nZ2dnNBrlcrmjoyOHw6mvr/f399+2bRvuaKClqEEkSVKv13O5XA6Ho9Ppzp49izsXaKnFixfn5eVxuVwWiyWTyZycnFgsFkEQZ86cwR3t6bi4A7RIeXk5m/1o+llWVoYQkkqlM2fOxJ0LtELDQaSQJIkvDmi1GTNmrFq1irqGsumZaCuDaBtbrzExMY1+oUFBQX379sWXCLRao0EkSTImJgZrItA6MTExQUFBDb9iQ4NoG003Z84cR0dH06cODg4wobM5s2bNanj5XUdHx9mzZ2NNBFpt5syZDS+HKJVK4+LisCZqKdtouqioqIbnrQ8JCYmOjsaaCLRa3759u3btavo0NDS0T58+WBOBVouJiWk4iN27d4+MjMSaqKVso+kQQnPnznVwcKAmdDNmzMAdBzyLuLg4akbg4uIC51i3UbNnz6YG0dnZ2YZm5TbTdJGRkdQaQXBwMEzobFTfvn2pq/SGhoZGRUXhjgOeRUxMDDWINjSha9G+V329sapEp6olLJLnSZ4fOq+2VDRuyPTcdCXuLEjswHVx5/P4LNxBWkSvM1Y9tIpBHD/sVVWFZGQ/GMRW0+uMVSU6VR3+QRw3JF5VIRnVf4YNDeJTjqe7dKwy95ZS6mInEHHaO6Ft02kM1RX1AeH2g19wxZ3lKS79VHnvltKpox1fCIP4L1q1QVFVH9DTftBkax/Eiz9U5t6qc3bn2/FhEP9FoyTqaoiuPe0HTHR5wrc9qenOfF3m7C4M7itt7hvA39cVFcWasa+44Q7SrF+/KnP1Egb3gUFsVvZ1hfyh5rk51juIv+wt7ewr6hYFg9isrOSamnLd6FmdmvuGZpsuMaHcxV3YtbeDOePRwb0btZXFmlEzm/0VY/Tbt+WunsKuvWAQn+JuWq28VDNiujUO4plvyjr7iv3DJbiDWLu/UxSKCu3waU0PYtN7JMoKdXqdEWquJbr2ctDXG8sLtbiDNFZWoCP0Rqi5lgjs7aDTGCse6HAHaawsX0uSLKi5lgiKkmrUZEVR04PYdNPJy3RcO5vZLYsdz44tK6vHnaIxWamOx4dBbCken11VanVNV1Wq49nZxg4Ta8CzY8ubeSY2/UxQKQhpRzszp6IPR1c7lQL/HrFGlLWEoysfdwqb4ehqZw37phtR1RlgEFtO6sJXNvNMbPooE9KACL1tvHHXGuj1JNf6TpVAEkajEQaxpfR6kmt9p/WBQWwVQk9ymtk1DVs3AAD6g6YDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQHzQdAID+2q3pJk4efvDbfQihn44dHj4S55VY161/a/mKRQihvLzcIcMiMzJuteSnrlz9Y+zzA1e/+6bZ81kxmx7EPy6dW7h49nNj+0+bPn7jpvcrKyssEdT62PQgJib++tqSV6hB/PiTdWq1ur3CWN8b0y2OIIiduzefPn3C3h7OAmar0tNvvLfh7UmTXn513us1NdVfbvnkQVHBls37cOcCrXDo+6/37ts2fPhzL0yZlpd378SJo0pV3fvvbWqXO4emQzk52deTr+zemfDllk9wZwHP6OtvdkdFRr++eDn1qUxWuX3H5wpFjVTq+LQfBdbi+IkjY8dMXP7maoTQ4EHDpVKnbds3ttcgmrfpxk8YMnXq7CpZ5fHjRxwdnfrFDpo1c96XWz+5du3PLl18ZkyfO2L4c0+9k6tXL23d/lllZUWAf+CkSS+PHvU8QkipVP7wY8Jff10rKMzr0MGlf7/BcXMWCASCZwjp5ua+c+e3EpjQNcMmBvG99zbW1dWaPu3Y0Q0hpNVqpXDpBYRsZRB/OHKm4ac8Ho/FYrHY7bPCZt49EnZ8/vfff+3nG5B4NmnuK4t+PX1ixVuLR44Yey7x+oD+QzZu2qBSqZ58D1evXlq7/r/xc1/7+KMt/foN/uTT9RcuJiKEfvzp0KHvv546dfahhJ9fX7z8/IWzCd/tf7aQzs4uUHNPYBODKLGXuHf2+OcRr12SSBxcXTs+273Rj00MYkOpade//mb3Sy/OcJC0z+UBzNt0LBarZ8/IcWMn8Xi8IYNHIoQiI6MHDRzG4XCGDB5ZX1//oKjgyffw1dc7Bw4YOnzY6KjI6Fkz4198YbpKpUQITX151r493w8aOMzJqUN0dP/Bg0akpCSZ9f/CWDY3iDdvpSYm/jpn9nx2O00HaMCGBvHA17uGDItc8d/FA/oPWTD/jbbcVUNmX6fz9fWnPhCLxQgh7y6+1KdCkQghpFTWPeFnDQZDfv59apJMWbTw/6gPeDzeXynXPv50XW5uDkEQCCEXF2u/ZKftsqFBTElNXrf+v/FzF0+e9HIb74pmbGUQR416vmfPyLt37xz8dq9CUbNubfusnpu96Visf13vo1Uvsyq1ymg0CoWix2/aseuL338//eq816MiYzp1ctu9Z8u582eaug/QDmxlEA99//X+r3bMnvXq9GlxbbkfWrKVQXTv7OHe2SOiZ2RIcI8lS+Ozsm6Hhoa15Q4pVr3vVSQUsVisx19tSJI8ffrESy/OGDd2EvWVJ78iAYwsNoinfj2+d9+2Nas/HDpkZFvuBzzOAoNYX19/8WJi165Bfn4B1FcCArohhEpKitql6ax6IYPL5XYN6JZ++4bpK3v3bdux84v6+nqtVutV+sbAAAARwklEQVTs/GiSXF9fn5R8GV9M8CSWGcR7uTlbtn669I2VUHPmYIFBZLPZX3z50W+Jp0xfuX//LkLIqYNzm+Mja286hNDkSVNTUpKOHP325q3Ukz//+P3hb/z9ugoEAg8Pr7O//VLysFihqPl043sRPSNraxVa7bNcXrrkYfHNW6k3b6UqlXUKRQ31cXW13Az/G4aywCDu3v2ll5d3ly4+1PBR/xSKGjP8bxjK3IPI5XInjH/x5M8/nDj5w81bqcnJVz7btMHb27dneO92yW/VW68IoVGjxtXWKb45uEelUjk7u8x/dcmoUeMQQu+u+Wj7jk1z4l4Q8AWvLV4eFt4rOfnK+IlDEg6eaO1DnDp17PCRg6ZPl725ACG09t2PBw8a3t7/G4aywCBmZd/WarXU2Jl8sOHz2NiB7fpfYS4LDOKr817ncDhff7Nboajhcrn9+w1etHAZj8drl/wso7GJi1xePyPX61H4oA7t8hi0l/6nnMtF0c9Z168r6VeZ0cjuMcAJdxDbAINIA7f+kPMFqM+oJgbR2rdeAQCg7fBvva55d/mtW6lN3jR+/Avz4l9r+0NMnDzcQBBN3vTO2xtiYga0/SEYDgaRBizwG8Y4iPibbukbK+v19U3eJBKJ2+Uhdu442NxNTo7WtbVio2AQacACv2GMg4i/6ZydXcz9EJ3d3M39EAwHg0gDFvgNYxxEWKcDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQX9NNxxexuXZQgi3F5bEFYqv7dQnEHDaX1YJvBAghxOWyhVY4iCIYxFbg8tgCEafJm5oeWkdXu7ICtZlT0UdZgdrRxQ53isacXHnlMIgtVpqvduxodYModeVVPNDgTmEzygrUjq5Nn8+u6abr0k2oVRmMpJlz0YKRRFqVoUs3Ie4gjXUJEmlgEFvGQBjrtQavrk1cEQYv7yCRSkGgJs4hCRqjBtGzmUFsuunYHNbASa6/f1di5mx0cO7Qw/4TXNgcq9vEYHNYAya6nDv0EHcQG3D+0MOBk1zZTW/34MThsvpPcIFnYkucO/Rw0ORmB7Hpcw5TKop0J3aWhA/u4OhiJxBb318BVjoVWV2lu/2nfPw8907eAtxxmlVeqP1578PwAR0cO/L5Iqtbh8JLqzLUVNanX5JPXOjR0YuPO06zSgu0v+4v7THAyakjny+EQfwXjdJQK9PfvFA1+XVPV49mB/FJTYcQ0mnIGxerK4t16tqmz59nYVVVVS4uZj9BUEuIHDiuHoKIwU5WuC+iEa2KvHmxpuqhVgWD+G8iCdfVk99rqJP114dGabj5R42sVKdS4B9EoxHJZNYyiEIJt5MXv/cwJx7/SYP4lKazNlFRUSkpKbhTgDaBQbR1BoMhNjb2+vXruIO0grW/lAEAQNtB0wEA6A+aDgBAf9B0AAD6g6YDANAfNB0AgP6g6QAA9AdNBwCgP2g6AAD9QdMBAOgPmg4AQH/QdAAA+oOmAwDQHzQdAID+oOkAAPQHTQcAoD9oOgAA/UHTAQDoD5oOAEB/0HQAAPqDpgMA0B80HQCA/qDpAACtZltXT0UIcXEHaJ2pU6cOHjw45n9cXV1xJwKtFhwcjDsCeBYVFRVJSUlJSUnJycnTpk3DHad1bOzK1gghpVJJ/a6vXbsmlUqjo6NjY2P79OmDOxdoKbiytW25fv36tWvXkpOT6+rqTJMMsViMO1fr2F7TNXT//n3qRSYlJcU0Bt7e3rhzgSeBprN+BQUFSf/Tp0+f2NjY6Ohof39/3LmenW03nQlJktQsLzk5WafTUQMTExMjEAhwRwONQdNZJ41GY9paEggEpqkDi8XCHa0d0KTpGiotLTW9HAUHB8fExERHR4eEhODOBR6BprMqWVlZycnJSUlJOTk51JMlNjbWzc0Nd652RsOma+jmzZvUKD58+JCa5UVHRzs7O+POxWjQdNjJZDLTvgVPT0/qqdGzZ0/cucyI5k1nolAoTEPr4uJCDW1kZCTuXEwETYdLSkoK9cIvl8tNL/xSqRR3LktgStM1lJOTQ413enq6abru6emJOxdTQNNZUlFRkWkxJyIigvqDDwwMxJ3L0pjYdCZ6vd60BIsQMi3B8ng83NHoDJrO3Orr60076FgslmkHHZdrY8fPtiNGN11DxcXFppe+sLAw6o+jW7duuHPREDSdmeTk5FB/wBkZGaaXbQ8PD9y5rAI0XRPS0tKo18PKykrTXwxDljMsAJquHdXU1Ji2Szp16kTN3Xr37o07l9WBpnsSuVxu2o/RuXNnaqIXERGBO5dtg6Zruxs3blBrzaWlpaYXYycnJ9y5rBc0XUvduXOHar07d+5Qf1jR0dHu7u64c9keaLpnU1JSQrVbUlJSaGgoNX2DNxG3EDRdq+l0OmrbNikpyc7OjvqDi4mJYbPhxDAtAk3XcgaDwbRVQRCE6Y+Nz+fjjmZjoOnapLCw0LQfIyoqijpgxabfHmgB0HRPlZubS72apqWlmTYg4A3dbQFN127++usvqvIUCoVp6cTe3h53LqsDTdekuro606umo6MjtSgMJ+lpL9B07a+ystL0J+vj40NVXlhYGO5c1gKarqH09HRqJaSwsND0Auni4oI7F91A05lXZmYmVXn379+ntm2jo6M7deqEOxcGo0aN4vF4RqOxrKysU6dObDabIIizZ8/izoVBeXm56bWwa9eu1B9GaGgo7lx0Bk1nISqVynTYulgsNi2+4M5lOb169Wq008ZoNKalpeFLZGmmA980Go3pZU8kEuHOxQjQdBjk5eVRr+fXr1+n+i4mJsbX1xd3LvOaP39+SkqKqeyMRmNUVNSuXbtw5zKvvLw8auM0OTnZtOeU9mNthaDpMDMdsKLRaEzLNEKhEHeu9nf58uW1a9fW1tZSn0ql0nXr1g0YMAB3rvanVqtNh4aIRCLTixnuXIwGTWctysrKTFs33bp1o54bNFu7efXVV2/cuEF9HBUVtXPnTtyJ2lNWVhb1unXv3j3TixYz12StEDSdNUpPT6da78GDB6bzStHgBKKXL19et26dQqGgzYSuqqrKNCv39vamBis8PBx3LtAYNJ1Vq62tNb0ByMnJiZomREVF4c717KhpXWRkpE2v0JmOnaypqTGdE0kikeDOBZoFTWcz7t27Rz27bt68adpz16VLF7M+KEkilYJQKw0k0T5/J+np6QcOHIiLi2uviQ+HyxJJOCIHrrnfjFdYWGjae967d2/qVScgIMC8jwraCTSd7TG9FzIpKclgMJgOWGnyvZAxMTFDhgz58MMPW37/+npjQZYq56ZKWUNUlWj4Qq6DK1+nMrTrf6Ld2Ak5dTKtTmNw8RRJpJzAXmKfEDHPrhWXs1q5cuWlS5eSkpIev0mn05l+1Twez/QCw+Fw2vU/AcwOms62lZSUmHbzde/endqMCgoKom59/vnnS0tL2Wx23759t27d+tR70+vIS8dkRffUPKGdvbNI4irmcG3mCngGwlhXqVJWqQmd3qurcOBkl5b03eLFi1NSUkiS9PLyOn78OPVF6rw1ycnJ2dnZpkND4Lw1Ng2ajj7S0tKoRb3y8nJqlvfxxx+r1Wrq1rCwsL179z5hMnLt1+r0S/JOAR06eDlYMLVZyIpqy+/JI4Z0iBnT7Cnb9Hp9fHx8dnY29RQQCoVvv/029bLRuXNn6hfYq1cvywYH5gJNR0PV1dXUlOTUqVOmI3VJkgwMDNy2bVuT76k89GkR38He2dvmO64hWaFCp1RPW97EtZAqKiqWLFmSk5PTsPrHjh1LzeAcHR0tmxSYHTQdnT1+mUdfX9+NGzc2PP9PvZbctybfp3dnkZSGpzxTVWuLbpXN3eDH4/+zJXv//v2VK1fm5+c3/E6SJE3H+gH6gZNH0taYMWOoD0iSNBgMRqNRLBbX1ta+//77pu+p15JHvigJGuRNy5pDCImdBIEDvA9/XqzX/fOK/umnn9bV1YlEIpIkSZKkXuzZbPa4ceOwhgVmBHM62oqNjXVychIKhQKBwMfHp0ePHv7+/j4+Pg23XveuzveN8uDa0XxPol5nKEwrid/wz7tNKyoq8vPz8/Ly/v7773v37qlUKoPBIJfLqethAvqBpqOz9PR0b2/v5ladft5TxpU4iBzpOZtrRCXXGrV14+a6NXlrTU1NQUFBz549LZ4LWAg0HUPdSam7dVnVOdgVdxDLeZhd0Xuwfbfe8E4GJoJ1Ooa6crLK1a8D7hQW5ernfOWkDHcKgAc0HROlX6518nDg8mm+PNcIT8CRutlnXqvFHQRgAE3HRBlXa8TO1nuq2x9OfrRp+wxz3LO4g+j2VYU57hlYOWg6xlEpCI3SIJTY4Q6CgVDKV1YT6jorfQ8vMB9oOsbJy1RJXK13Qmduko6i/CwV7hTA0ri4AwBLKyvQ8cVmPLLketrP11NPlJXf7+zWNbz7sAExU1ksFkJozQfDhw6crdWpzl86IOCLu3WNmTBmmYPEGSGk06m/+/Hd3LzUzp0C+vV9wXzZEEJ8Mb+sQBsaTav3vYGngjkd4ygVBJdvrle4tFtnfjjxgad78NvLjo8a+uqf177/+cxm6iYej3/hz294PP6Gd86tWHIkv/DWuT/2UzcdPfFBlaxo/pxts//zSUnp3Zx7yWaKhxDi8jlKBWy9Mg40HeOoagnz7XVNTj3h5x0x+fkVEvsOgQF9Rg+bf/X6DypVDUIIIZaXR/DwQXFCoUTq4NrVv09hURZCSFFbmZ55bkj/md5e3R0kzuNGvc7jmnENkWfHUdUS5rt/YJ2g6RiHL+LyeGZpOoOBKCzKCOza1/SVAL9IkjTkF6ZTn3p6BJtuEgokWp0SISSvLkEIder46K1aLBbL0z3IHPEoHDsuX8isw2sArNMxkUFP1msJrqD9n+31ei1JGs6e23X23L+uEVGnkv/vwyZOjalSKxBCAr696St2dma8CKReqzfoSfPdP7BO0HSMI5ZyiHoCofbfKSEU2NvxBJER48JChzb8uotzE2eI+yePSIoQ0hM601e0OjPuGyV0BrEU/uwZB4accVw688vKzPVm585uXev1mgC/3tSneqK+urrUUfqka546ObojhAqLMjw6ByKECEKfm5fq4GCuN+SSBqOzBxOPJWQ4WKdjnE7efJXMXJOmsSMX3866cD3tZ5Ik8wpuJhxZtfvr1/R63RN+xFHa0adL+Nlzu6pkRXq9LuGH1SxzXuZLKVO6eQvMd//AOkHTMY5vqLimTG2mO/fziVi64Jv8glvrPhm955slWp0qbvpnPN5TtpT/M2Wtp0fw59tnrHp/iFgojYoYZyTNtZSmKNf4hDD3wGnGgrM2MdGp/WWIL7F3ZtzUpq5KwyFUY+KetDUNaAnmdEzUc6BUXlSNOwUG8gc1PQfCuyOYCPZIMJFnV6HInqWUaeydmz6e40ry0bPndzd5k8Gg53B4Td40bcr6kKD+7RXyjysJ5y4daPImocBBo2365Etx0z7z92360oV1VRqJI9vd34yHsACrBVuvDFVZXJ/4faVH96bPNq6r1+i0Te+10OrUAn7T61xCkUM7vr1Bp1PrdE2vJ+qJ+uYeSCSScrlNF3FxRtlzM12dO8OOVyaCpmOu62erC3OJjv6MOPNwRa7cN4gXNQIu5MpQsE7HXH1HO4nFpLy4DncQs5MV1UkcSKg5JoM5HdP99l2VSs3t4Enb68hUPVBIpeSIqS4t+F5AWzCnY7pR0134HF3FfXkLvtf2lN+vFgsIqDkAczqAEELXf6u+d1vr2FnS3N5Ym6OUaRSltV3DhX1GOuHOAvCDpgOPlBfq/jxRpdMiJ09HcQcbPqhYKdNUF9cIRKxBk1w6ejHiut3gqaDpwL8U39NkXKvNy6hzdheLnMQsDosr4PLMdo7idmBEeh1B6AjSYFRXq+QlKv+ekh4xDh4BNJmcgnYBTQeaQBpQXqayslhXXqRT1hA8O46i8knv0sfIwZVv0JNiKcfNi+/qxfftbm/O8wMAWwVNBwCgP3j5AwDQHzQdAID+oOkAAPQHTQcAoD9oOgAA/UHTAQDoD5oOAEB//w/+/ywUF3L74QAAAABJRU5ErkJggg==",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Image(router_workflow.get_graph().draw_mermaid_png()))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "296a0164-8e2f-4094-aa90-f8aff58d95b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "state = router_workflow.invoke({\"input\": \"Write me a joke about cats\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "2a503563-5641-4472-b585-29f39b929229",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'llm_call_router': {'decision': 'joke'}}\n",
      "{'llm_call_2': {'output': 'Why was the cat sitting on the computer?\\n\\nBecause it wanted to keep an eye on the mouse!'}}\n"
     ]
    }
   ],
   "source": [
    "for step in router_workflow.stream({\"input\": \"Write me a joke about cats\"}):\n",
    "    print(step)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "bbbadb86-bb1c-4f4f-bf40-c1b978866294",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'llm_call_router': {'decision': 'poem'}}\n",
      "{'llm_call_3': {'output': 'In sunlit corners, shadows play,  \\nWhere whispers of the feline sway,  \\nWith graceful poise and silent tread,  \\nThe world’s a kingdom, theirs to thread.  \\n\\nA tapestry of fur like night,  \\nWith emerald eyes that pierce the light,  \\nThey leap as if on dreams they dance,  \\nIn elegant arcs, a fleeting glance.  \\n\\nA soft purr hums, a gentle song,  \\nA lullaby where hearts belong,  \\nWith velvet paws on wooden floors,  \\nThey weave their magic, open doors.  \\n\\nEach flick of tail, a tale to tell,  \\nOf mischief, grace, and worlds that dwell  \\nIn boxes, sunbeams, every fold,  \\nAdventures vast, and secrets bold.  \\n\\nThey curl like commas, snug and warm,  \\nIn every lap, their soft charm forms,  \\nA soothing presence, quiet, wise,  \\nWith knowing hearts and ageless sighs.  \\n\\nOh, creatures of the night and day,  \\nIn your soft wisdom, we find our way,  \\nWith tender gazes, you understand,  \\nThe joys and sorrows of this land.  \\n\\nSo here’s to cats, our quaintest friends,  \\nWith every whisker, affection lends,  \\nIn their elusive, gentle grace,  \\nWe find a home, a purr-fect place.'}}\n"
     ]
    }
   ],
   "source": [
    "for step in router_workflow.stream({\"input\": \"Write me a poem about cats\"}):\n",
    "    print(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25bb63a6-c711-4dd6-a696-e98f46a32524",
   "metadata": {},
   "source": [
    "### Orchestrator-Worker (协调器-工作器)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "1e2c09fe-fd72-4519-a647-370f5b23fa33",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/worker.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/worker.png', width=500)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8cec7e8-ed6d-4bed-be75-449ad3071beb",
   "metadata": {},
   "source": [
    "- an orchestrator breaks down a task and delegates each sub-task to workers.\n",
    "    - In the orchestrator-workers workflow, a central LLM dynamically **breaks down tasks**, **delegates them to worker LLMs**, and **synthesizes their results**.\n",
    "    - When to use this workflow: This workflow is well-suited for complex tasks where you can’t predict the subtasks needed (in coding, for example, the number of files that need to be changed and the nature of the change in each file likely depend on the task). Whereas it’s topographically similar, the key difference from parallelization is its flexibility—subtasks aren't pre-defined, but determined by the orchestrator based on the specific input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "65090cca-f739-4121-b8ab-e40adb7c146c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, List\n",
    "import operator\n",
    "\n",
    "\n",
    "# Schema for structured output to use in planning\n",
    "class Section(BaseModel):\n",
    "    name: str = Field(\n",
    "        description=\"Name for this section of the report.\",\n",
    "    )\n",
    "    description: str = Field(\n",
    "        description=\"Brief overview of the main topics and concepts to be covered in this section.\",\n",
    "    )\n",
    "\n",
    "\n",
    "class Sections(BaseModel):\n",
    "    sections: List[Section] = Field(\n",
    "        description=\"Sections of the report.\",\n",
    "    )\n",
    "\n",
    "\n",
    "# Augment the LLM with schema for structured output\n",
    "planner = llm.with_structured_output(Sections)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "10c023a9-7fc5-4a5e-8524-a317b177be3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.constants import Send\n",
    "\n",
    "\n",
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    topic: str  # Report topic\n",
    "    sections: list[Section]  # List of report sections\n",
    "    completed_sections: Annotated[\n",
    "        list, operator.add\n",
    "    ]  # All workers write to this key in parallel\n",
    "    final_report: str  # Final report\n",
    "\n",
    "\n",
    "# Worker state\n",
    "class WorkerState(TypedDict):\n",
    "    section: Section\n",
    "    completed_sections: Annotated[list, operator.add]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "67a9970b-bc03-4aeb-b96b-0ae3f1723631",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nodes\n",
    "def orchestrator(state: State):\n",
    "    \"\"\"Orchestrator that generates a plan for the report\"\"\"\n",
    "\n",
    "    # Generate queries\n",
    "    report_sections = planner.invoke(\n",
    "        [\n",
    "            SystemMessage(content=\"Generate a plan for the report.\"),\n",
    "            HumanMessage(content=f\"Here is the report topic: {state['topic']}\"),\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    return {\"sections\": report_sections.sections}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "43fadef9-e8d6-4202-b191-f2fc731db5a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def llm_call(state: WorkerState):\n",
    "    \"\"\"Worker writes a section of the report\"\"\"\n",
    "\n",
    "    # Generate section\n",
    "    section = llm.invoke(\n",
    "        [\n",
    "            SystemMessage(\n",
    "                content=\"Write a report section following the provided name and description. Include no preamble for each section. Use markdown formatting.\"\n",
    "            ),\n",
    "            HumanMessage(\n",
    "                content=f\"Here is the section name: {state['section'].name} and description: {state['section'].description}\"\n",
    "            ),\n",
    "        ]\n",
    "    )\n",
    "\n",
    "    # Write the updated section to completed sections\n",
    "    return {\"completed_sections\": [section.content]}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "4fa28176-5c82-4681-a0fe-9fd5a646e5ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "def synthesizer(state: State):\n",
    "    \"\"\"Synthesize full report from sections\"\"\"\n",
    "\n",
    "    # List of completed sections\n",
    "    completed_sections = state[\"completed_sections\"]\n",
    "\n",
    "    # Format completed section to str to use as context for final sections\n",
    "    completed_report_sections = \"\\n\\n---\\n\\n\".join(completed_sections)\n",
    "\n",
    "    return {\"final_report\": completed_report_sections}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "dd9c481e-659b-4ad3-8e2f-fecdbd55f6dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Conditional edge function to create llm_call workers that each write a section of the report\n",
    "def assign_workers(state: State):\n",
    "    \"\"\"Assign a worker to each section in the plan\"\"\"\n",
    "\n",
    "    # Kick off section writing in parallel via Send() API\n",
    "    return [Send(\"llm_call\", {\"section\": s}) for s in state[\"sections\"]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "242068a3-bf1b-46f1-b52e-b09b4b8fe2d1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Build workflow\n",
    "orchestrator_worker_builder = StateGraph(State)\n",
    "\n",
    "# Add the nodes\n",
    "orchestrator_worker_builder.add_node(\"orchestrator\", orchestrator)\n",
    "orchestrator_worker_builder.add_node(\"llm_call\", llm_call)\n",
    "orchestrator_worker_builder.add_node(\"synthesizer\", synthesizer)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "orchestrator_worker_builder.add_edge(START, \"orchestrator\")\n",
    "orchestrator_worker_builder.add_conditional_edges(\n",
    "    \"orchestrator\", assign_workers, [\"llm_call\"]\n",
    ")\n",
    "orchestrator_worker_builder.add_edge(\"llm_call\", \"synthesizer\")\n",
    "orchestrator_worker_builder.add_edge(\"synthesizer\", END)\n",
    "\n",
    "# Compile the workflow\n",
    "orchestrator_worker = orchestrator_worker_builder.compile()\n",
    "\n",
    "# Show the workflow\n",
    "display(Image(orchestrator_worker.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "id": "5274e662-6477-4247-9547-15bb4c385abe",
   "metadata": {},
   "outputs": [],
   "source": [
    "state = orchestrator_worker.invoke({\"topic\": \"Create a report on LLM scaling laws\"})\n",
    "\n",
    "from IPython.display import Markdown\n",
    "# Markdown(state[\"final_report\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "2bfda291-5077-4895-830b-0be53daba818",
   "metadata": {},
   "outputs": [],
   "source": [
    "# for step in orchestrator_worker.stream({\"topic\": \"Create a report on LLM scaling laws\"}):\n",
    "#     print(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10a62e7f-6a67-44cb-a111-0173fbf5ab5e",
   "metadata": {},
   "source": [
    "### Evaluator-optimizer (Actor-Critic)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "ff6ccea7-1db0-4603-9e73-e2db06694f84",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/evaluator_optimizer.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/evaluator_optimizer.png', width=500)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfe782f1-467b-4f34-9013-d8db7f1b0302",
   "metadata": {},
   "source": [
    "- In the evaluator-optimizer workflow, one LLM call generates a response while another provides evaluation and feedback in a loop.\n",
    "- When to use this workflow: This workflow is particularly effective when we have **clear evaluation criteria**, and when iterative refinement provides measurable value. The two signs of good fit are, first, that LLM responses can be demonstrably improved when a human articulates their feedback; and second, that the LLM can provide such feedback. This is analogous to the iterative writing process a human writer might go through when producing a polished document."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "69e8ddb9-8cd7-407b-9137-c89de5f9f665",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Graph state\n",
    "class State(TypedDict):\n",
    "    joke: str\n",
    "    topic: str\n",
    "    feedback: str\n",
    "    funny_or_not: str\n",
    "\n",
    "\n",
    "# Schema for structured output to use in evaluation\n",
    "class Feedback(BaseModel):\n",
    "    grade: Literal[\"funny\", \"not funny\"] = Field(\n",
    "        description=\"Decide if the joke is funny or not.\",\n",
    "    )\n",
    "    feedback: str = Field(\n",
    "        description=\"If the joke is not funny, provide feedback on how to improve it.\",\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "8c196092-06c3-4c67-a847-ec86ed30af70",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Augment the LLM with schema for structured output\n",
    "evaluator = llm.with_structured_output(Feedback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "bcc15e81-81fe-40b6-a460-37e9103e49c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def llm_call_generator(state: State):\n",
    "    \"\"\"LLM generates a joke\"\"\"\n",
    "\n",
    "    if state.get(\"feedback\"):\n",
    "        msg = llm.invoke(\n",
    "            f\"Write a joke about {state['topic']} but take into account the feedback: {state['feedback']}\"\n",
    "        )\n",
    "    else:\n",
    "        msg = llm.invoke(f\"Write a joke about {state['topic']}\")\n",
    "    return {\"joke\": msg.content}\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "21698a43-91f6-400d-8342-5717a33e791a",
   "metadata": {},
   "outputs": [],
   "source": [
    "def llm_call_evaluator(state: State):\n",
    "    \"\"\"LLM evaluates the joke\"\"\"\n",
    "\n",
    "    grade = evaluator.invoke(f\"Grade the joke {state['joke']}\")\n",
    "    return {\"funny_or_not\": grade.grade, \"feedback\": grade.feedback}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "3f609fed-769b-4dbe-b950-cefba92234b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Conditional edge function to route back to joke generator or end based upon feedback from the evaluator\n",
    "def route_joke(state: State):\n",
    "    \"\"\"Route back to joke generator or end based upon feedback from the evaluator\"\"\"\n",
    "\n",
    "    if state[\"funny_or_not\"] == \"funny\":\n",
    "        return \"Accepted\"\n",
    "    elif state[\"funny_or_not\"] == \"not funny\":\n",
    "        return \"Rejected + Feedback\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "48c7ec0a-e20f-4a70-8f38-f72dd555f318",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Build workflow\n",
    "optimizer_builder = StateGraph(State)\n",
    "\n",
    "# Add the nodes\n",
    "optimizer_builder.add_node(\"llm_call_generator\", llm_call_generator)\n",
    "optimizer_builder.add_node(\"llm_call_evaluator\", llm_call_evaluator)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "optimizer_builder.add_edge(START, \"llm_call_generator\")\n",
    "optimizer_builder.add_edge(\"llm_call_generator\", \"llm_call_evaluator\")\n",
    "optimizer_builder.add_conditional_edges(\n",
    "    \"llm_call_evaluator\",\n",
    "    route_joke,\n",
    "    {  # Name returned by route_joke : Name of next node to visit\n",
    "        \"Accepted\": END,\n",
    "        \"Rejected + Feedback\": \"llm_call_generator\",\n",
    "    },\n",
    ")\n",
    "\n",
    "# Compile the workflow\n",
    "optimizer_workflow = optimizer_builder.compile()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "f897a32f-9aa8-4502-9988-15c3c6176cb4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display(Image(optimizer_workflow.get_graph().draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "e1e0c0ad-2c4b-493b-af96-a52b77d36b6b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'llm_call_generator': {'joke': 'Why was the cat sitting on the computer?\\n\\nBecause it wanted to keep an eye on the mouse!'}}\n",
      "{'llm_call_evaluator': {'funny_or_not': 'funny', 'feedback': ''}}\n"
     ]
    }
   ],
   "source": [
    "for step in optimizer_workflow.stream({\"topic\": \"Cats\"}):\n",
    "    print(step)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2093a9ff-6f71-4269-aa9c-c870144e8750",
   "metadata": {},
   "source": [
    "### Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1fff939-f869-4dc9-a0e2-388fd19bf005",
   "metadata": {},
   "source": [
    "- Environment 接受 Action 返回 feedback"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "57e9abbf-d6fa-46db-976f-5d0426219e4d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://langchain-ai.github.io/langgraph/tutorials/workflows/img/agent.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://langchain-ai.github.io/langgraph/tutorials/workflows/img/agent.png', width=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "9b8ba482-0797-49e4-a182-2f0f89d9d4fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<img src=\"https://www.ibm.com/content/dam/connectedassets-adobe-cms/worldwide-content/creative-assets/s-migr/ul/g/8f/5e/reinforcement-learning-figure-1.png\" width=\"500\"/>"
      ],
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Image(url='https://www.ibm.com/content/dam/connectedassets-adobe-cms/worldwide-content/creative-assets/s-migr/ul/g/8f/5e/reinforcement-learning-figure-1.png', width=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "491915e4-2dcb-40ce-9ef8-e4f61beb286b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "1f2c41d0-5041-4920-89fc-3b2fba4cc565",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "\n",
    "# Define tools\n",
    "@tool\n",
    "def multiply(a: float, b: float) -> float:\n",
    "    \"\"\"Multiply a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first float\n",
    "        b: second float\n",
    "    \"\"\"\n",
    "    return a * b\n",
    "\n",
    "\n",
    "@tool\n",
    "def add(a: float, b: float) -> float:\n",
    "    \"\"\"Adds a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first float\n",
    "        b: second float\n",
    "    \"\"\"\n",
    "    return a + b\n",
    "\n",
    "@tool\n",
    "def subtract(a: float, b: float) -> float:\n",
    "    \"\"\"subtract a from b.\n",
    "\n",
    "    Args:\n",
    "        a: first float\n",
    "        b: second float\n",
    "    \"\"\"\n",
    "    return a - b\n",
    "\n",
    "@tool\n",
    "def divide(a: float, b: float) -> float:\n",
    "    \"\"\"Divide a and b.\n",
    "\n",
    "    Args:\n",
    "        a: first float\n",
    "        b: second float\n",
    "    \"\"\"\n",
    "    return a / b\n",
    "\n",
    "@tool\n",
    "def sigmoid(a: float) -> float:\n",
    "    \"\"\"sigmoid(a)\n",
    "    Args: \n",
    "        a: first float\n",
    "    \"\"\"\n",
    "    return 1./(1+np.exp(-a))\n",
    "\n",
    "\n",
    "# Augment the LLM with tools\n",
    "tools = [add, subtract, multiply, divide, sigmoid]\n",
    "tools_by_name = {tool.name: tool for tool in tools}\n",
    "llm_with_tools = llm.bind_tools(tools)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "44b7adc4-443a-4c81-bb31-72496a708d4a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<IPython.core.display.Image object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from langgraph.graph import MessagesState\n",
    "from langchain_core.messages import SystemMessage, HumanMessage, ToolMessage\n",
    "\n",
    "\n",
    "# Nodes\n",
    "def llm_call(state: MessagesState):\n",
    "    \"\"\"LLM decides whether to call a tool or not\"\"\"\n",
    "\n",
    "    return {\n",
    "        \"messages\": [\n",
    "            llm_with_tools.invoke(\n",
    "                [\n",
    "                    SystemMessage(\n",
    "                        content=\"You are a helpful assistant tasked with performing arithmetic on a set of inputs.\"\n",
    "                    )\n",
    "                ]\n",
    "                + state[\"messages\"]\n",
    "            )\n",
    "        ]\n",
    "    }\n",
    "\n",
    "\n",
    "def tool_node(state: dict):\n",
    "    \"\"\"Performs the tool call\"\"\"\n",
    "\n",
    "    result = []\n",
    "    for tool_call in state[\"messages\"][-1].tool_calls:\n",
    "        tool = tools_by_name[tool_call[\"name\"]]\n",
    "        observation = tool.invoke(tool_call[\"args\"])\n",
    "        result.append(ToolMessage(content=observation, tool_call_id=tool_call[\"id\"]))\n",
    "    return {\"messages\": result}\n",
    "\n",
    "\n",
    "# Conditional edge function to route to the tool node or end based upon whether the LLM made a tool call\n",
    "def should_continue(state: MessagesState) -> Literal[\"environment\", END]:\n",
    "    \"\"\"Decide if we should continue the loop or stop based upon whether the LLM made a tool call\"\"\"\n",
    "\n",
    "    messages = state[\"messages\"]\n",
    "    last_message = messages[-1]\n",
    "    # If the LLM makes a tool call, then perform an action\n",
    "    if last_message.tool_calls:\n",
    "        return \"Action\"\n",
    "    # Otherwise, we stop (reply to the user)\n",
    "    return END\n",
    "\n",
    "\n",
    "# Build workflow\n",
    "agent_builder = StateGraph(MessagesState)\n",
    "\n",
    "# Add nodes\n",
    "agent_builder.add_node(\"llm_call\", llm_call)\n",
    "agent_builder.add_node(\"environment\", tool_node)\n",
    "\n",
    "# Add edges to connect nodes\n",
    "agent_builder.add_edge(START, \"llm_call\")\n",
    "agent_builder.add_conditional_edges(\n",
    "    \"llm_call\",\n",
    "    should_continue,\n",
    "    {\n",
    "        # Name returned by should_continue : Name of next node to visit\n",
    "        \"Action\": \"environment\",\n",
    "        END: END,\n",
    "    },\n",
    ")\n",
    "agent_builder.add_edge(\"environment\", \"llm_call\")\n",
    "\n",
    "# Compile the agent\n",
    "agent = agent_builder.compile()\n",
    "\n",
    "# Show the agent\n",
    "display(Image(agent.get_graph(xray=True).draw_mermaid_png()))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "4f69bd4a-f91a-40ec-b84a-978257dd573e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================\u001b[1m Human Message \u001b[0m=================================\n",
      "\n",
      "calculate derivative of sigmoid(5)\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  sigmoid (call_yhe0L1h0iirYx86Oc4tGbHHm)\n",
      " Call ID: call_yhe0L1h0iirYx86Oc4tGbHHm\n",
      "  Args:\n",
      "    a: 5\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "0.9933071490757153\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "Tool Calls:\n",
      "  multiply (call_eepwJz1ggN5uMOU3hCNOir1V)\n",
      " Call ID: call_eepwJz1ggN5uMOU3hCNOir1V\n",
      "  Args:\n",
      "    a: 0.9933071490757153\n",
      "    b: 0.006692850924284857\n",
      "=================================\u001b[1m Tool Message \u001b[0m=================================\n",
      "\n",
      "0.006648056670790157\n",
      "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
      "\n",
      "The derivative of the sigmoid function at \\( \\text{sigmoid}(5) \\) is approximately \\( 0.00665 \\).\n"
     ]
    }
   ],
   "source": [
    "\n",
    "# Invoke\n",
    "messages = [HumanMessage(content=\"calculate derivative of sigmoid(5)\")]\n",
    "messages = agent.invoke({\"messages\": messages})\n",
    "for m in messages[\"messages\"]:\n",
    "    m.pretty_print()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4511ec93-e6f6-412c-a1c1-3ed730d65542",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e334123b-f95a-48bc-8e42-a4df2163e212",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
